/**
 * File		: RubixCubeView.java
 * Subject	: Sets the view and generates the world for the rubix cube
 * Author	: Isaac Parenteau (locus2k@gmail.com)
 * Date		: April 16th, 2011
 */
package src.project.rubix;

import src.project.cube.Cube;
import src.project.cube.Face;
import src.project.cube.Layer;
import src.project.cube.Shape;
import src.project.cube.World;
import src.project.util.Color;
import android.content.Context;
import android.graphics.PixelFormat;
import android.opengl.GLSurfaceView;
import android.util.Log;
import android.view.MotionEvent;

public class RubixCubeView  extends GLSurfaceView implements RubixCubeRenderer.AnimationCallback{

	public static final String name = RubixCubeView.class.getSimpleName();
	
    // names for our 9 layers (based on notation from http://www.cubefreak.net/notation.html)
    public static final int Up = 0;
    public static final int Down = 1;
    public static final int Left = 2;
    public static final int Right = 3;
    public static final int Front = 4;
    public static final int Back = 5;
    public static final int Middle = 6;
    public static final int Equator = 7;
    public static final int Side = 8;
	
	private float xPos = 0;
	private float yPos = 0;
	private float previousFaceValue;
    private float currentAngle, endAngle; // current and final angle for current Layer animation
    private float angleIncrement; // amount to increment angle
    private float startX = -1f;
    private float startY = -1f;
    
    private boolean animating = false;
    private boolean direction = false;
    
    private int layerID = -1;
	private int previousFace = 0;
    private int[] currentLayerPermutation;
    private int[] permutation;// current permutation of starting position

	private RubixCubeRenderer renderer;
	private Cube[] cubes = new Cube[27];
	private World world;
	private Cube previousCube = null;
	private Cube selectedCube = null;

	
	private Color yellow = new Color(255, 255, 0, 255);
	private Color purple = new Color(233, 0, 255, 255);
	private Color orange = new Color(255, 127, 80, 255);
	
    private Layer currentLayer = null; // currently turning layer
    private Layer[] layers = new Layer[9];


	
	/**
	 * Constructor
	 * @param context
	 */
	public RubixCubeView(Context context) {
		super(context);
		
		world = makeWorld();
		renderer = new RubixCubeRenderer(world, this);
		getHolder().setFormat(PixelFormat.RGBA_8888);
		setRenderer(renderer);
	}

	/**
	 * Generates the rubix cube world
	 * @return
	 */
	private World makeWorld() {
		Log.v(name, "Generating World");
		World world = new World();
		
		Log.v(name, "Generating Colors");
		
		GenerateCubes(world);

        Log.v(name, "Painting Cubes");
     // paint the sides
        int i, j;
        // set all faces black by default
        for (i = 0; i < 27; i++) {
            Cube cube = cubes[i];
            if (cube != null) {
                for (j = 0; j < 6; j++)
                    cube.setFaceColor(j, Color.Black);
            }
        }

        // paint top
        int count = 0;
        for (i = 0; i < 9; i++) {
			Color shade = Color.Green.shade(1+count++);
			cubes[i].setFaceColor(Cube.Top, shade);
		}
        
        // paint bottom
        count = 0;
        for (i = 18; i < 27; i++) {
			Color shade = orange.shade(1+count++);
			cubes[i].setFaceColor(Cube.Bottom, shade);
		}
        // paint left
        count = 0;
        for (i = 0; i < 27; i += 3){
            Color shade = Color.Blue.shade(1+count++);
        	cubes[i].setFaceColor(Cube.Left, shade);
        }
        // paint right
        count = 0;
        for (i = 2; i < 27; i += 3){
            Color shade = yellow.shade(1+count++);
        	cubes[i].setFaceColor(Cube.Right, shade);
        }
        // paint back
        count = 0;
        for (i = 0; i < 27; i += 9)
            for (j = 0; j < 3; j++){
            	Color shade = purple.shade(1+count++);
                cubes[i + j].setFaceColor(Cube.Back, shade);
            }
        // paint front
        count = 0;
        for (i = 6; i < 27; i += 9)
            for (j = 0; j < 3; j++){
            	Color shade = Color.Red.shade(1+count++);
                cubes[i + j].setFaceColor(Cube.Front, shade);
            }
        

        Log.v(name, "Adding Cubes to the world");
        for (i = 0; i < 27; i++)
            if (cubes[i] != null)
                world.addShape(cubes[i]);
        
        
        //Initializes our permutation to solved position
        permutation = new int[27];
        for(i = 0; i < permutation.length; i++)
        	permutation[i] = i;
        
        createLayers();
        updateLayers();
        
        for(Cube c : cubes)
        	if(c != null)
        		Log.v(name, c.printCurrentLayer());
        
        world.generate();
        Log.v("RubixCubeView", "Finished Generating world");
        
		return world;
	}

	/**
	 * Updates the layers
	 */
	private void updateLayers() {
		Layer layer;
		Shape[] shapes;
		int i, j, k;
		
		for(Cube c : cubes)
			if(c != null)
				c.clearCurrentLayer();
		
		//Up layer
		layer = layers[Up];
		shapes = layer.Shapes;
		for(i = 0; i < 9; i++){
			shapes[i] = cubes[permutation[i]];
			cubes[permutation[i]].updateCurrentLayer(Up);
		}
		
		//Down layer
		layer = layers[Down];
		shapes = layer.Shapes;
		for(i = 18, k = 0; i < 27; i++){
			shapes[k++] = cubes[permutation[i]];
			cubes[permutation[i]].updateCurrentLayer(Down);
		}
		
        // left layer
        layer = layers[Left];
        shapes = layer.Shapes;
        for (i = 0, k = 0; i < 27; i += 9)
            for (j = 0; j < 9; j += 3){
                shapes[k++] = cubes[permutation[i + j]];
                cubes[permutation[i + j]].updateCurrentLayer(Left);
            }

        // right layer
        layer = layers[Right];
        shapes = layer.Shapes;
        for (i = 2, k = 0; i < 27; i += 9)
            for (j = 0; j < 9; j += 3){
                shapes[k++] = cubes[permutation[i + j]];
                cubes[permutation[i + j]].updateCurrentLayer(Right);
            }

        // front layer
        layer = layers[Front];
        shapes = layer.Shapes;
        for (i = 6, k = 0; i < 27; i += 9)
            for (j = 0; j < 3; j++){
                shapes[k++] = cubes[permutation[i + j]];
                cubes[permutation[i + j]].updateCurrentLayer(Front);
            }

        // back layer
        layer = layers[Back];
        shapes = layer.Shapes;
        for (i = 0, k = 0; i < 27; i += 9)
            for (j = 0; j < 3; j++){
                shapes[k++] = cubes[permutation[i + j]];
                cubes[permutation[i +j]].updateCurrentLayer(Back);
            }

        // middle layer
        layer = layers[Middle];
        shapes = layer.Shapes;
        for (i = 1, k = 0; i < 27; i += 9)
            for (j = 0; j < 9; j += 3){
                shapes[k++] = cubes[permutation[i + j]];
                if(cubes[permutation[i + j]] != null)
                	cubes[permutation[i + j]].updateCurrentLayer(Middle);
            }

        // equator layer
        layer = layers[Equator];
        shapes = layer.Shapes;
        for (i = 9, k = 0; i < 18; i++){
            shapes[k++] = cubes[permutation[i]];
            if(cubes[permutation[i]] != null)
            	cubes[permutation[i]].updateCurrentLayer(Equator);
        }

        // side layer
        layer = layers[Side];
        shapes = layer.Shapes;
        for (i = 3, k = 0; i < 27; i += 9)
            for (j = 0; j < 3; j++){
                shapes[k++] = cubes[permutation[i + j]];
                if(cubes[permutation[i + j]] != null)
                	cubes[permutation[i + j]].updateCurrentLayer(Side);
            }
	}

	/**
	 * Creates the animation layers
	 */
	private void createLayers() {
		layers[Up] = new Layer(Layer.yAxis);
		layers[Down] = new Layer(Layer.yAxis);
		layers[Left] = new Layer(Layer.xAxis);
		layers[Right] = new Layer(Layer.xAxis);
		layers[Front] = new Layer(Layer.zAxis);
		layers[Back] = new Layer(Layer.zAxis);
		layers[Middle] = new Layer(Layer.xAxis);
		layers[Equator] = new Layer(Layer.yAxis);
		layers[Side] = new Layer(Layer.zAxis);
	}
	
	/**
	 * Generates the cube for the world
	 * @param world
	 */
	private void GenerateCubes(World world) {
		Log.v(name, "Generating Coordinates");
		//Coordinates for our cubes
        float left = -1.0f;
        float bottom = -0.38f;
        float back = -0.32f;
        float right = 0.32f;
        float top = 0.38f;
        float front = 1.0f;
		
        Log.v(name, "Generating Cubes");
        // top back, left to right
        cubes[0]  = new Cube(world, left, top, left, bottom, front, bottom, 0);
        cubes[1]  = new Cube(world, back, top, left, right, front, bottom, 1);
        cubes[2]  = new Cube(world, top, top, left, front, front, bottom, 2);
        // top middle, left to right
        cubes[3]  = new Cube(world, left, top, back, bottom, front, right, 3);
        cubes[4]  = new Cube(world, back, top, back, right, front, right, 4);
        cubes[5]  = new Cube(world, top, top, back, front, front, right, 5);
        // top front, left to right
        cubes[6]  = new Cube(world, left, top, top, bottom, front, front, 6);
        cubes[7]  = new Cube(world, back, top, top, right, front, front, 7);
        cubes[8]  = new Cube(world, top, top, top, front, front, front, 8);
        // middle back, left to right
        cubes[9]  = new Cube(world, left, back, left, bottom, right, bottom, 9);
        cubes[10] = new Cube(world, back, back, left, right, right, bottom, 10);
        cubes[11] = new Cube(world, top, back, left, front, right, bottom, 11);
        // middle middle, left to right
        cubes[12] = new Cube(world, left, back, back, bottom, right, right, 12);
        cubes[13] = null;
        cubes[14] = new Cube(world, top, back, back, front, right, right, 14);
        // middle front, left to right
        cubes[15] = new Cube(world, left, back, top, bottom, right, front, 15);
        cubes[16] = new Cube(world, back, back, top, right, right, front, 16);
        cubes[17] = new Cube(world, top, back, top, front, right, front, 17);
        // bottom back, left to right
        cubes[18] = new Cube(world, left, left, left, bottom, bottom, bottom, 18);
        cubes[19] = new Cube(world, back, left, left, right, bottom, bottom, 19);
        cubes[20] = new Cube(world, top, left, left, front, bottom, bottom, 20);
        // bottom middle, left to right
        cubes[21] = new Cube(world, left, left, back, bottom, bottom, right, 21);
        cubes[22] = new Cube(world, back, left, back, right, bottom, right, 22);
        cubes[23] = new Cube(world, top, left, back, front, bottom, right, 23);
        // bottom front, left to right
        cubes[24] = new Cube(world, left, left, top, bottom, bottom, front, 24);
        cubes[25] = new Cube(world, back, left, top, right, bottom, front, 25);
        cubes[26] = new Cube(world, top, left, top, front, bottom, front, 26);
        
        selectedCube = cubes[0];
        
        Log.v(name, "Finished generating cubes");
	}
	
	/**
	 * Test the cube to see what face was selected
	 * @param selectedCube selected cube
	 * @param c color that was selected
	 */
	private void TestCube(Cube selectedCube, Color c) {
		
		if(previousCube != null)
			world.updateColorBuffer(previousCube.getFace(previousFace), previousFaceValue);
		
		if(c.isTypeOf(Color.Red)){
			previousFaceValue = world.updateColorBuffer(selectedCube.getFace(Cube.Front), 0.5f);
			previousFace = Cube.Front;
		}
		else if(c.isTypeOf(Color.Green)){
			previousFaceValue = world.updateColorBuffer(selectedCube.getFace(Cube.Top), 0.7f);
			previousFace = Cube.Top;
		}
		else if(c.isTypeOf(Color.Blue)){
			previousFaceValue = world.updateColorBuffer(selectedCube.getFace(Cube.Left), 0.5f);
			previousFace = Cube.Left;
		}
		else if(c.isTypeOf(yellow)){
			previousFaceValue = world.updateColorBuffer(selectedCube.getFace(Cube.Right), 0.5f);
			previousFace = Cube.Right;
		}
		else if(c.isTypeOf(orange)){
			previousFaceValue = world.updateColorBuffer(selectedCube.getFace(Cube.Bottom), 0.5f);
			previousFace = Cube.Bottom;
		}
		else if(c.isTypeOf(purple)){
			previousFaceValue = world.updateColorBuffer(selectedCube.getFace(Cube.Back), 0.5f);
			previousFace = Cube.Back;
		}
		
		previousCube = selectedCube;
	}

	/**
	 * On touch event handler
	 */
	public boolean onTouchEvent(final MotionEvent event){
		queueEvent(new Runnable(){

			@Override
			public void run() {
			
				if(animating)
					return;
				
				if(event.getAction() == MotionEvent.ACTION_MOVE){ 
					if(xPos == 0)
						xPos = event.getX();
					if(yPos == 0)
						yPos = event.getY();
					
					if(!selectedCube.Selected){
						if(xPos > event.getX())
							renderer.updateYRotation(-3f);
						else if(xPos < event.getX())
							renderer.updateYRotation(3f);
						
						if(yPos < event.getY())
							renderer.updateXRotation(3f);
						else if(yPos > event.getY())
							renderer.updateXRotation(-3f);
						
						xPos = event.getX();
						yPos = event.getY();	
					}
					else{
						
					}
				}
				
				if(event.getAction() == MotionEvent.ACTION_DOWN && !selectedCube.Selected){
					
					startX = event.getX();
					startY = event.getY();
					Color c = Color.getPixelColor(renderer.getPixels(startX, startY));
					
					for(int i = 0; i < cubes.length; i++){
						if(cubes[i] == null) continue;
						Cube currentCube = cubes[i];
						for(int j = 0; j < currentCube.getFaceCount(); j++){
							Face f = currentCube.getFace(j);
							Color color = f.getColor();
							if(Color.equal(c, color, 1)){
								selectedCube = cubes[i];
							}
						}
					}
					if(selectedCube != null){
						TestCube(selectedCube, c);
						selectedCube.Selected = true;
						Log.v(name, selectedCube.printCurrentLayer());
					}
				}
				
				if(event.getAction() == MotionEvent.ACTION_UP && selectedCube.Selected){
					float endX = event.getX();
					float endY = event.getY();

					float angle = (float) Math.atan2((double)(endY - startY), 
													 (double)(endX - startX));
					
					//Normalizes the angle
					float twopi = (float)Math.PI * 2f;
					while(angle >= twopi) angle -= twopi;
					while(angle < 0f) angle += twopi;
					
					angle = (float) Math.toDegrees(angle);
					
					//left right
					if((angle > 0f && angle < 5f) || (angle > 330f && angle < 350f))
						LeftRight();
					else if(angle > 160f && angle <191f){
						//direction = true;
						LeftRight();
					}
					//diagonal
					else if(angle > 30f && angle < 50f)
						Diagonal();
					else if(angle > 200f && angle < 228f){
						//direction = true;
						Diagonal();
					}
					//up down
					else if(angle < 110f && angle > 80f)
						UpDown();
					else if(angle > 260f && angle < 286f){
						//direction = true;
						UpDown();
					}			
				}
			}
		});
		return true;
	}

	/**
	 * Rotates the cube layer vertically
	 */
	private void UpDown() {
		if(selectedCube.isValidLayer(Left))
			layerID = Left;
		else if (selectedCube.isValidLayer(Right))
			layerID = Right;
		else if(selectedCube.isValidLayer(Middle))
			layerID = Middle;
		
		animating = true;
	}

	/**
	 * Rotates the cube layer by the face
	 */
	private void Diagonal() {
		if(selectedCube.isValidLayer(Front))
			layerID = Front;
		else if (selectedCube.isValidLayer(Side))
			layerID = Side;
		else if(selectedCube.isValidLayer(Back))
			layerID = Middle;
		
		animating = true;
	}

	/**
	 * Rotates the cube layer horizontally
	 */
	private void LeftRight() {
		
		if(selectedCube.isValidLayer(Up))
			layerID = Up;
		else if (selectedCube.isValidLayer(Down))
			layerID = Down;
		else if(selectedCube.isValidLayer(Equator))
			layerID = Equator;
		
		animating = true;
	}

	@Override
	public void animate() {
		
		if(!animating || layerID == -1)
			return;
		
		if(currentLayer == null){
			currentLayer = layers[layerID];
			currentLayerPermutation = layerPermutations[layerID];
			currentLayer.startAnimation();
			currentAngle = 0;

			if(direction){
				angleIncrement = (float)Math.PI / 30;
                endAngle = currentAngle + ((float)Math.PI) / 2f;
			}
			else{
				angleIncrement = -(float)Math.PI / 30;
				endAngle = currentAngle - ((float)Math.PI) /2f;
			}
		}
		
		currentAngle += angleIncrement;
		
		if((angleIncrement > 0f && currentAngle >= endAngle) ||
				(angleIncrement <0f && currentAngle <= endAngle)){
			currentLayer.setAngle(endAngle);
			currentLayer.endAnimation();
			currentLayer = null;
		
			int[] newPermutation = new int[27];
			for(int i = 0; i < 27; i++)
				newPermutation[i] = permutation[currentLayerPermutation[i]];
			permutation = newPermutation;
			updateLayers();
			selectedCube.Selected = false;
			animating = false;
			direction = false;
			
			if (previousCube != null)
				world.updateColorBuffer(previousCube.getFace(previousFace), previousFaceValue);
			
		}
		else{
			currentLayer.setAngle(currentAngle);
		}
	}
	
	static int[][] layerPermutations = {
        // permutation for UP layer
        { 2, 5, 8, 1, 4, 7, 0, 3, 6, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26 },
        // permutation for DOWN layer
        { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 20, 23, 26, 19, 22, 25, 18, 21, 24 },
        // permutation for LEFT layer
        { 6, 1, 2, 15, 4, 5, 24, 7, 8, 3, 10, 11, 12, 13, 14, 21, 16, 17, 0, 19, 20, 9, 22, 23, 18, 25, 26 },
        // permutation for RIGHT layer
        { 0, 1, 8, 3, 4, 17, 6, 7, 26, 9, 10, 5, 12, 13, 14, 15, 16, 23, 18, 19, 2, 21, 22, 11, 24, 25, 20 },
        // permutation for FRONT layer
        { 0, 1, 2, 3, 4, 5, 24, 15, 6, 9, 10, 11, 12, 13, 14, 25, 16, 7, 18, 19, 20, 21, 22, 23, 26, 17, 8 },
        // permutation for BACK layer
        { 18, 9, 0, 3, 4, 5, 6, 7, 8, 19, 10, 1, 12, 13, 14, 15, 16, 17, 20, 11, 2, 21, 22, 23, 24, 25, 26 },
        // permutation for MIDDLE layer
        { 0, 7, 2, 3, 16, 5, 6, 25, 8, 9, 4, 11, 12, 13, 14, 15, 22, 17, 18, 1, 20, 21, 10, 23, 24, 19, 26 },
        // permutation for EQUATOR layer
        { 0, 1, 2, 3, 4, 5, 6, 7, 8, 11, 14, 17, 10, 13, 16, 9, 12, 15, 18, 19, 20, 21, 22, 23, 24, 25, 26 },
        // permutation for SIDE layer
        { 0, 1, 2, 21, 12, 3, 6, 7, 8, 9, 10, 11, 22, 13, 4, 15, 16, 17, 18, 19, 20, 23, 14, 5, 24, 25, 26 }
	};
}
